/*
 * XMPP.java
 *
 * Created on April 17, 2007, 9:55 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.restlet.xmpp;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingException;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentSource;
import org.restlet.Response;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.xeerkat.xmpp.ClientConnection;
import org.xeerkat.xmpp.ID;

/**
 *
 * @author alex
 */
public class XMPP
{
   static public final String XMPP_ATTR = XMPP.class.getName()+".instance";
   
   static Logger LOG = Logger.getLogger(XMPP.class.getName());
   static public final Protocol XEERKAT_PROTOCOL = Protocol.valueOf("xeerkat");
   static public final Protocol XEERKAT_SYNC_PROTOCOL = Protocol.valueOf("sxeerkat");
   static public final Protocol XMPP_PROTOCOL = Protocol.valueOf("xmpp");
   
   public interface ResponseListener {
      public void onResponse(UUID id, Response response);
      public void onOffline(ID id);
      public void onOnline(ID id);
      public void onManagement(ID from,String method,String username,String password,Document message);
   }
   public class Identity {
      ID id;
      ID originalId;
      ClientConnection connection;
      List<ResponseListener> listeners;
      List<ResponseListener> selfListeners;
      String xmppid;
      
      Identity(ID id)
         throws NamingException
      {
         this.id = null;
         this.originalId = id;
         this.connection = null;
         listeners = new ArrayList<ResponseListener>();
         selfListeners = new ArrayList<ResponseListener>();
      }

      public String getHost() {
         return getId().toString().replace('/','.').replace("@","%40");
      }
      
      public ID getId() {
         return originalId;
      }
      
      public ID getConnectionId() {
         return id;
      }
      
      public void connect() 
         throws XMPPException
      {
         try {
            connection = ClientConnection.lookupByDomain(originalId.getDomain());
            connection.connect(originalId);
            id = connection.getBindID();
            identities.put(id,this);
            connection.sendPresence(true);
            connection.startKeepAlive();
            notifySelf(id,true);
         } catch (Exception ex) {
            throw new XMPPException(ex.getMessage(),ex);
         }
      }

      protected void notifySelf(ID id, boolean online) {
         List<ResponseListener> copyOfSelfListeners = new ArrayList<ResponseListener>(selfListeners);
         for (ResponseListener listener : copyOfSelfListeners) {
            if (online) {
               listener.onOnline(id);
            } else {
               listener.onOffline(id);
            }
         }
      }
      
      public boolean wasConnected() {
         return connection!=null && connection.getBindID()!=null;
      }
      
      public boolean isConnected() {
         return connection!=null && connection.isConnected();
      }
      
      public void reconnect() 
         throws XMPPException
      {
         if (id!=null) {
            identities.remove(id);
         }
         connect();
      }
      
      public void failed()
      {
         try {
            connection.close();
         } catch (Exception ex) {
            // toss exception
         }
         connection.stopReceiver();
         connection.stopKeepAlive();
         notifySelf(id,false);
      }
      
      public void disconnect()
         throws XMPPException
      {
         if (connection==null) {
            return;
         }
         try {
            
            if (connection.isConnected()) {
               connection.close();
            }
            connection.stopReceiver();
            connection.stopKeepAlive();
            notifySelf(connection.getBindID()==null ? id : connection.getBindID(),false);
         } catch (Exception ex) {
            throw new XMPPException(ex.getMessage(),ex);
         }
         
      }
      
      public void sendOnline(ID id)
         throws XMPPException
      {
         try {
            connection.sendPresence(id,true);
         } catch (Exception ex) {
            throw new XMPPException(ex.getMessage(),ex);
         }
      }
      
      public void sendProbe(ID id)
         throws XMPPException
      {
         try {
            connection.sendProbe(id);
         } catch (Exception ex) {
            throw new XMPPException(ex.getMessage(),ex);
         }
      }
      
      public void sendManagement(ID to,String authMethod,String username,String password,Document message)
         throws XMPPException
      {
         if (!isConnected()) {
            connect();
         }
         ItemDestination stanza = connection.getStanzaDestination();
         if (stanza==null) {
             throw new XMPPException("Disconnected during send.");
         }

         try {
            ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
            Element messageE = constructor.createElement(org.xeerkat.xmpp.Protocol.MESSAGE);
            messageE.setAttributeValue("from",connection.getBindID().toString());
            messageE.setAttributeValue("to",to.toString());
            stanza.send(messageE);
            Element upgradeE = constructor.createElement(MessageSequence.MANAGE);
            if (authMethod!=null) {
               upgradeE.setAttributeValue("method",authMethod);
            }
            if (username!=null) {
               upgradeE.setAttributeValue("username",username);
            }
            if (password!=null) {
               upgradeE.setAttributeValue("password",password);
            }
            stanza.send(upgradeE);
            DocumentSource.generate(message.getDocumentElement(), false,stanza);
            stanza.send(constructor.createElementEnd(MessageSequence.MANAGE));
            stanza.send(constructor.createElementEnd(org.xeerkat.xmpp.Protocol.MESSAGE));
         } catch (XMLException ex) {
            throw new XMPPException("Cannot send upgrade message.",ex);
         }
      }
      
      public List<ResponseListener> getSelfListeners() {
         return selfListeners;
      }
      
      
      public void addResponseListener(ResponseListener listener) {
         listeners.add(listener);
      }
      
      public void notifyResponse(UUID id, Response response) {
         for (ResponseListener listener : listeners) {
            listener.onResponse(id,response);
         }
      }
      
      public void notifyOffline(ID id)
      {
         if (connection!=null && connection.getBindID()!=null && id.equals(connection.getBindID())) {
            notifySelf(id,false);
         }
         for (ResponseListener listener : listeners) {
            listener.onOffline(id);
         }
      }
      
      public void notifyOnline(ID id)
      {
         if (connection!=null && connection.getBindID()!=null && id.equals(connection.getBindID())) {
            notifySelf(id,true);
         }
         for (ResponseListener listener : listeners) {
            listener.onOnline(id);
         }
      }
      
      public void notifyManagement(ID from,String authMethod,String username,String password,Document message)
      {
         if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Upgrade message from "+from);
         }
         for (ResponseListener listener : listeners) {
            listener.onManagement(from,authMethod,username,password,message);
         }
      }
      /*
      public static Identity parse(String identity)
      {
         int atSign = identity.indexOf('@');
         if (atSign<1) {
            throw new RuntimeException("Invalid or missing '@' in identity: "+identity);
         }
         String name = identity.substring(0,atSign);
         String rest = identity.substring(atSign+1);
         int slash = rest.indexOf('/');
         if (slash==0) {
            throw new RuntimeException("Missing server name in "+identity);
         }
         if (slash<0) {
            return new Identity(name,rest,"xeerkat");
         } else {
            String resource = rest.substring(slash+1);
            if (resource.length()==0) {
               resource = "xeerkat";
            }
            return new Identity(name,rest.substring(0,slash),resource);
         }
      }
       */
      
   }
   
   Map<ID,Identity> identities;
   Identity defaultIdentity;
   
   /** Creates a new instance of XMPP */
   public XMPP()
   {
      this.identities = new TreeMap<ID,Identity>();
      this.defaultIdentity = null;
   }
   
   public Identity addIdentity(ID id)
      throws NamingException
   {
    
      Identity ido = new Identity(id);
      this.identities.put(id,ido);
      if (defaultIdentity==null) {
         defaultIdentity = ido;
      }
      return ido;
   }
   
   public Identity getIdentity(ID id)
   {
      if (id.getResource()==null) {
         // search linearly
         for (Identity identity : identities.values()) {
            if (identity.getId().isSameAuthority(id)) {
               return identity;
            }
         }
         return null;
      } else {
         return identities.get(id);
      }
   }

   public void setDefaultIdentity(Identity ido) {
      this.defaultIdentity  = ido;
   }

   public Identity getDefaultIdentity() {
      return this.defaultIdentity;
   }
   
   public void connect() 
      throws XMPPException
   {
      List<Identity> values = new ArrayList<Identity>();
      values.addAll(identities.values());
      for (Identity id : values) {
         id.connect();
      }
   }
   
}
